﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Toenda.Foundation {
	/// <summary>
	/// Implements the base class for all data member objects
	/// </summary>
	public abstract class CloneableObject<T> : IDeepCloneable<T> {
		/// <summary>
		/// Clone this object
		/// </summary>
		/// <typeparam name="T">The target type.</typeparam>
		/// <returns>Returns a clone of the current object.</returns>
		public static T Clone(T objectToClone) {
			object obj = objectToClone;
			return (T)((CloneableObject<T>)obj).Clone();
		}

		/// <summary>
		/// Returns a string that represents the current object
		/// </summary>
		/// <returns></returns>
		public override string ToString() {
			BindingFlags conOptions = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

			StringBuilder str = new StringBuilder();

			if(this != null) {
				foreach(PropertyInfo pi in this.GetType().GetProperties(conOptions)) {
					try {
						if(pi.CanRead) {
							str.Append(pi.Name);
							str.Append("==");
							str.Append(pi.GetValue(this, null));
							str.Append(";");
						}
					}
					catch(Exception) {
					}
				}
			}

			return str.ToString();
		}

		/// <summary>
		/// Simple clone methodClone this object
		/// </summary>
		/// <returns>Returns a clone of the current object.</returns>
		public T Clone() {
			BindingFlags conOptions = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

			if(this != null) {
				object clone = Activator.CreateInstance(this.GetType());

				if(clone != null) {
					foreach(PropertyInfo pi in this.GetType().GetProperties(conOptions)) {
						try {
							if(pi.CanRead) {
								// read property type
								Type propertyType = pi.PropertyType;

								// if property type is a list, read the list
								// and clone each item of the list
								if(propertyType.IsGenericType
								&& typeof(List<>) == propertyType.GetGenericTypeDefinition()) {
									Type listType = propertyType.GetGenericArguments()[0];

									//  if object is from type IDataMember
									if(listType.BaseType.Name == typeof(CloneableObject<>).Name) {
										object objContent = pi.GetValue(this, null);

										// get the property from the target object
										PropertyInfo p = clone.GetType().GetProperty(pi.Name, conOptions);

										// check if the property exist and is writable
										if(p.CanWrite) {
											// create new list of same object as the source list
											Type internalListType = typeof(List<>).MakeGenericType(new[] { listType });
											object list = Activator.CreateInstance(internalListType);

											// invoke the clone method from each item in the
											// source list and add the clone to the new list
											foreach(var item in (System.Collections.IList)objContent) {
												((System.Collections.IList)list).Add(
													item.GetType().GetMethod("Clone").Invoke(item, null)
												);
											}

											// now write the new clones list
											p.SetValue(clone, list, null);
										}
									}
									else {
										// otherwise only write the whole list as one property
										PropertyInfo p = clone.GetType().GetProperty(pi.Name, conOptions);

										if(p.CanWrite) {
											p.SetValue(clone, pi.GetValue(this, null), null);
										}
									}
								}
								// check for DataMember type
								else if(propertyType.BaseType.Name == typeof(CloneableObject<>).Name) {
									PropertyInfo p = clone.GetType().GetProperty(pi.Name, conOptions);

									p.SetValue(
										clone,
										propertyType.GetMethod("Clone").Invoke(pi.GetValue(this, null), null),
										null
									);
								}
								else {
									// copy the value from source property to target property
									PropertyInfo p = clone.GetType().GetProperty(pi.Name, conOptions);

									if(p.CanWrite) {
										p.SetValue(clone, pi.GetValue(this, null), null);
									}
								}
							}
						}
						catch(Exception ex) {
							//Console.WriteLine(ex.ToString());

							try {
								PropertyInfo p = clone.GetType().GetProperty(pi.Name, conOptions);

								if(p.CanWrite) {
									p.SetValue(clone, pi.GetValue(this, null), null);
								}
							}
							catch(Exception ex2) {
								//Console.WriteLine(ex2.ToString());
							}
						}
					}
				}

				return (T)clone;
			}

			return default(T);
		}
	}
}
